<template>
  <div class="main_view">
    <div class="selectFile" @click="selectFile">
      <input ref="uploadFile" type="file" class="uploadFile">
      <span class="iconfont icon-cloudupload" />
      <div class="el-upload__text">将文件拖到此处，或<em>点击上传</em></div>
    </div>
    <div class="upload_button">
      <button type="primary" @click="handleUpload" class="ml-16">
        开始上传
      </button>
      <button type="primary" @click="handlePause" class="ml-16">
        暂停上传
      </button>
      <button type="primary" @click="handleResume" class="ml-16">
        继续上传
      </button>
    </div>
    <div class="upload_progress">
      <div v-for="item of data" :key="item.hash">
        <div class="filename">
          {{item.filename}}
        </div>
        <div class="ant-progress-inner">
          <div class="ant-progress-bg" :style="{width: `${item.percentage}%`}"></div>
        </div>
      </div>
    </div>

    <div>
      <div>文件名： {{fileName}}</div>
      <div>上传进度： {{uploadPercentage}}</div>
    </div>
    <div>
      <li v-for="item of data" :key="item.hash">{{item.fileHash}}-----{{item.percentage}}</li>
    </div>
  </div>

</template>

<script>
  import Worker from './script/workers/hash.worker'
  const SIZE = 1 * 1024 * 1024
  export default {
    data: () => ({
      container: {
        file: null
      },
      data: (() => {
        const arr = []
        for (let i = 0; i < 10; i++) {
          arr.push({ hash: i, percentage: 10 * i, filename: '文件' + i })
        }
        return arr
      })(),
      requestList: []
    }),
    inject: ['http'],
    computed: {
      fileName () {
        return this.container.file && this.container.file.name
      },
      uploadPercentage () {
        if (!this.container.file || !this.data.length) return 0

        const loaded = this.data
          .map(item => item.chunk.size * item.percentage)
          .reduce((acc, cur) => acc + cur)

        return parseInt((loaded / this.container.file.size).toFixed(2))
      }
    },
    mounted () {
      // this.test()
    },
    methods: {
      // 初始化
      initSelectFile () {
        const [file] = this.$refs.uploadFile.files
        if (!file) return
        Object.assign(this.$data, this.$options.data())
        this.container.file = file
      },
      selectFile (e) {
        this.$refs.uploadFile.dispatchEvent(new MouseEvent('click'))
      },
      handlePause () {
        this.requestList.forEach(v => v.abort())
        this.requestList = []
      },
      async handleResume () {
        const { hasFile, uploadedList } = await this.verifyUpload(this.container.file.name, this.container.hash)
        await this.uploadChunk(uploadedList)
      },
      // 处理上传文件
      async handleUpload () {
        debugger
        this.initSelectFile()
        const fileChunkList = this.createFileChunk(this.container.file)
        this.container.hash = await this.createFileHash(fileChunkList)
        this.data = fileChunkList.map(({ file }, index) => ({
          chunk: file,
          fileHash: this.container.hash + '-' + index,
          index,
          percentage: 0,
          // 生成文件切片时，需要给每个切片一个标识作为hash,后端合并的时候这是第几个切片
          hash: this.container.file.name + '----' + index
        }))
        const { hasFile, uploadList } = await this.verifyUpload(this.container.file.name, this.container.hash)
        if (hasFile) {
          this.data.map(v => v.percentage = 100)
          window.alert('上传成功')
          return
        }
        await this.uploadChunk()
      },
      // 生成文件 hash(web-worker)
      createFileHash (fileChunkList) {
        return new Promise(resovle => {
          this.container.worker = new Worker('./Hash.js')
          // 调用postMessage 向worker线程发送消息 ---》
          this.container.worker.postMessage({ fileChunkList })
          //  worker.onmessage 指定监听函数，接收子线程发送回来的消息
          this.container.worker.onmessage = e => {
            const { percentage, hash } = e.data
            this.hashPercentage = percentage
            if (hash) resovle(hash)
          }
          // Worker 完成任务以后，主线程就可以把它关掉。worker.terminate();
          // this.container.worker.terminate()
        })
      },
      // 创建文件切片
      createFileChunk (file, size = SIZE) {
        const chunkList = []
        let cur = 0
        while (cur < file.size) {
          chunkList.push({
            file: file.slice(cur, cur + size)
          })
          cur += size
        }
        return chunkList
      },
      // 上传切片
      async uploadChunk (uploadedList = []) {
        let requestList = []
        requestList = this.data
          .filter(({ fileHash }) => !uploadedList.includes(fileHash))
          .map(({ chunk, fileHash, index }) => {
            const formData = new FormData()
            formData.append('chunk', chunk)
            formData.append('hash', fileHash)
            formData.append('filename', this.container.file.name)
            formData.append('filehash', this.container.hash)
            return { formData, index }
          })
        requestList = requestList.map(({ formData, index }) => {
          return this.requestFile(formData, index)
        })
        await this.sendRequset(requestList)
        this.mergeRequest()
      },
      requestFile (formData, index) {
        this.data[index].status = 'doing'
        return this.http({
          url: '/file',
          data: formData,
          onProgress: this.createProgressHandler(this.data[index]),
          requestList: this.requestList
        }).then(res => {
          this.data[index].status = 'done'
        }).catch(err => {
          const errorNum = this.data[index].errorNum
          if (!errorNum) {
            this.data[index].errorNum = 1
          } else if (errorNum > 3) {
            return err
          }
          this.data[index].status = 'error'
          this.data[index].errorNum++
          this.requestFile(formData, index)
        })
      },
      // 上传进度
      createProgressHandler (item) {
        return e => {
          item.percentage = parseInt((e.loaded / e.total) * 100)
        }
      },
      // 异步请求并发控制
      async sendRequset (requestList, max = 4) {
        const list = []
        for (let i = 0; i < requestList.length; i++) {
          list.push(requestList.slice(i, i + max))
          i = i + max - 1
        }
        const len = list.length
        let num = 0
        while (num < 2) {
          await Promise.all(list[num])
          num++
        }
      },
      // 检查文件是否已经上传
      async verifyUpload (filename, filehash) {
        const { data } = await this.http({
          url: '/verify',
          headers: {
            'content-type': 'application/json'
          },
          data: JSON.stringify({
            filename,
            filehash
          })
        })
        // console.log(data)
        return JSON.parse(data)
      },
      // 合并切片
      mergeRequest () {
        this.http({
          url: "/merge",
          headers: {
            "content-type": "application/json"
          },
          data: JSON.stringify({
            filename: this.container.file.name,
            filehash: this.container.hash
          })
        });
      }
    }
  }
</script>
<style scoped lang="less">
  .selectFile {
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: #fff;
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    box-sizing: border-box;
    width: 360px;
    height: 180px;
    text-align: center;
    cursor: pointer;
    overflow: hidden;
    flex-direction: column;
    & > .uploadFile {
      display: none;
    }

    & > .iconfont {
      font-size: 67px;
      color: #c0c4cc;
      margin: 0 0 16px;
      line-height: 50px;
    }

    & > .el-upload__text {
      color: #606266;
      font-size: 14px;
      text-align: center;
      height: 20px;
    }
  }

  .upload_button {
    display: flex;
    height: 100px;
    width: 360px;
    justify-content: center;
    align-items: center;
    flex-direction: row;

    & > button[type="primary"] {
      color: #fff;
      background-color: #409eff;
      border-color: #409eff;
      padding: 9px 15px;
      font-size: 12px;
      border-radius: 3px;
      text-align: center;
      box-sizing: border-box;
      border: 1px solid #dcdfe6;
    }
    & > .ml-16 {
      margin-left: 16px;
    }
  }

  .upload_progress {
    display: flex;
    width: 480px;
    height: 640px;
    align-items: flex-start;
    flex-wrap: wrap;
    flex-direction: row;
    overflow: auto;

    & > div {
      width: 100px;
      height: 100px;
      padding: 8px;
      margin: 16px 8px;
      border: 1px solid #d9d9d9;
      border-radius: 2px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;

      & .filename {
        color: rgba(0, 0, 0, 0.85);
        font-size: 16px;
        margin-bottom: 32px;
      }
      & .ant-progress-inner {
        position: relative;
        display: inline-block;
        width: 100%;
        overflow: hidden;
        vertical-align: middle;
        background-color: #f5f5f5;
        border-radius: 100px;

        & > .ant-progress-bg {
          position: relative;
          background-color: #1890ff;
          border-radius: 100px;
          height: 6px;
          width: 50%;
        }
      }
    }
  }
</style>